#!/usr/bin/env python3
# coding: utf-8
# Copyright 2025 Huawei Technologies Co., Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
# ===========================================================================

import abc
import inspect
import json
import re
from typing import Dict


class JsonDict(metaclass=abc.ABCMeta):

    @staticmethod
    def _is_json_dict_in_typing_list(instance, sign_class):
        return isinstance(instance, list) and \
            isinstance(getattr(sign_class, "__args__"), tuple) and \
            len(sign_class.__args__) == 1 and \
            issubclass(sign_class.__args__[0], JsonDict)

    @classmethod
    def from_dict(cls, json_dict: Dict):
        sigs = inspect.signature(cls.__init__)
        args = []
        for arg_name, parameter in sigs.parameters.items():
            if arg_name == "self":
                continue
            value = json_dict.get(arg_name)
            if value is None:
                args.append(None)
                continue
            sign_class = parameter.annotation
            if isinstance(sign_class, type) and issubclass(sign_class, JsonDict):
                value = sign_class.from_dict(value)
            elif cls._is_json_dict_in_typing_list(value, sign_class):
                value = [sign_class.__args__[0].from_dict(item) for item in value]
            args.append(value)
        return cls(*args)

    @classmethod
    def from_json(cls, json_dict_str):
        return cls.from_dict(json.loads(json_dict_str))

    def to_dict(self):
        members = {}
        for key, value in vars(self).items():
            if key.startswith('__'):
                continue
            if isinstance(value, JsonDict):
                value = value.to_dict()
            elif isinstance(value, list):
                new_value = []
                for item in value:
                    if isinstance(item, JsonDict):
                        new_value.append(item.to_dict())
                    else:
                        new_value.append(item)
                value = new_value
            members[key] = value
        return members

    def to_json(self):
        return json.dumps(self.to_dict())


def check_safe_command_arg(arg: str):
    """
    校验外部参数是否安全，避免 Shell 注入风险（适用于 shell=True 场景）。

    安全规则：
    1. 仅允许字母（a-z, A-Z）、数字（0-9）
    2. 允许部分安全符号：- _ . / : @ （常见于路径、URL、文件名）
    3. 禁止所有 Shell 元字符（; & | > < ' " \ ( ) $ ` * ? [ ] { }）

    Args:
        arg: 待校验的外部参数（字符串类型）

    Returns:
        bool: True 表示安全，False 表示存在注入风险
    """
    # 定义安全字符集的正则表达式（^ 表示开头，$ 表示结尾，确保整个字符串都匹配）
    # 允许的字符：字母、数字、- _ . / : @
    unsafe_pattern = r'^[;&\|]+$'

    # 检查是否完全匹配安全模式
    if re.search(unsafe_pattern, str(arg)):
        raise RuntimeError("Parameter contains command injection: {}".format(arg))
